package clue.players.ai;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.javatuples.Triplet;

import clue.EnumerationEngine2;
import clue.cards.Card;
import clue.cards.RoomCard;
import clue.cards.SuspectCard;
import clue.cards.WeaponCard;
import clue.engine.GameState;
import clue.enums.Room;
import clue.enums.Suspect;
import clue.enums.Weapon;
import clue.players.Player;

public class AIPlayer extends Player {
	private EnumerationEngine2 engine;
	private HashMap<Integer, Integer> IDToOpponentIdx;
	private int id;
	public int n = 0;
	@Override
	public void gameStarted(GameState gs) {
		Card[] cards = new Card[gs.getOwnHand(this).size()];
		gs.getOwnHand(this).toArray(cards);
		engine = new EnumerationEngine2(gs.playerIDs.size(), cards);
		IDToOpponentIdx = new HashMap<>();
		int myID = gs.getOwnID(this);
		id = myID;
		List<Integer> ids = gs.playerIDs;
		int i;
		for (i = 0; i < ids.size(); i++) {
			if (ids.get(i) == myID)
				break;
		}
		int index = 0;
		for (int j = (i + 1) % ids.size(); j != i; j = (j + 1) % ids.size(), index++) {
			IDToOpponentIdx.put(ids.get(j), index);
		}
	}

	private int suspect;
	private int weapon;
	
	public int getID() {
		return id;
	}
	
	public double percentile = 0;
	private double lastEntropy = 0;
	private int progress;
	
	private class Element implements Comparable{
		int w, s, r;
		
		Element(int w, int s, int r) {
			this.w =w ;
			this.s = s;
			this.r = r;
		}

		@Override
		public int compareTo(Object arg0) {
			if (arg0 == null)
				return 1;
			if (!(arg0 instanceof Element))
				return 1;
			Element b = (Element) arg0;
			if (engine.envProb[w][s][r] < engine.envProb[b.w][b.s][b.r])
				return -1;
			else if (engine.envProb[w][s][r] > engine.envProb[b.w][b.s][b.r])
				return 1;
			else return 0;
		}
		
	}
	
	@Override
	public Room move(GameState gs) {
		ArrayList<Element> elements = new ArrayList<>();
		int room = -1, suspect = -1, weapon = -1;
		double maxProb = 2;
		for (int r = 0; r < 9; r++) {
			if (Room.values()[r].equals(gs.IDToLocation.get(gs.getOwnID(this))))
				continue;
			for (int s = 0; s < 6; s++) {
				for (int w = 0; w < 6; w++) {
					elements.add(new Element(w,s,r));
				}
			}
		}
		Collections.sort(elements);
		room = elements.get(elements.size() - 1 - n).r;
		suspect = elements.get(elements.size() - 1 - n).s;
		weapon = elements.get(elements.size() - 1 - n).w;

		//System.out.println("maxProb = " + maxProb + " delta = " + lastEntropy);
		
		progress = 0;
		lastEntropy = engine.entropy;
		this.percentile = (elements.size() - 1 - n) / (double) elements.size();
		this.suspect = suspect;
		this.weapon = weapon;
		
		return Room.values()[room];
	}

	@Override
	public Triplet<Room, Suspect, Weapon> suggest(GameState gs) {
		return new Triplet<Room, Suspect, Weapon>(gs.IDToLocation.get(gs.getOwnID(this)), Suspect.values()[suspect],Weapon.values()[weapon]);
	}

	@Override
	public Card refute(GameState gs, Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		for (Card c : gs.getOwnHand(this)) {
			if (c instanceof RoomCard) {
				RoomCard rc = (RoomCard) c;
				if (rc.getRoom().equals(suggestion.getValue0()))
					return rc;
			} else if (c instanceof SuspectCard) {
				SuspectCard sc = (SuspectCard) c;
				if (sc.getSuspect().equals(suggestion.getValue1()))
					return sc;
			} else if (c instanceof WeaponCard) {
				WeaponCard wc = (WeaponCard) c;
				if (wc.getWeapon().equals(suggestion.getValue2()))
					return wc;
			}
		}
		return null;
	}

	@Override
	public void informMove(GameState gs, Integer movingPlayerID, Room room) {
	}

	@Override
	public void informSuggestion(GameState gs, Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		// we'll assume no trickery for now
		// TODO: do not assume no trickery
//		engine.mustNotExist(new Card[] { new RoomCard(suggestion.getValue0()),
//										 new SuspectCard(suggestion.getValue1()),
//										 new WeaponCard(suggestion.getValue2())
//									   },
//							this.IDToOpponentIdx.get(suggestingPlayerID));
	}

	@Override
	public void informFailureToRefute(GameState gs, Integer failedPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		engine.mustNotExist(new Card[] { new RoomCard(suggestion.getValue0()),
				 						 new SuspectCard(suggestion.getValue1()),
				 						 new WeaponCard(suggestion.getValue2())
			   						   },
			   				this.IDToOpponentIdx.get(failedPlayerID));
		if (progress >= 0) {
			progress++;
			if (progress == gs.playerIDs.size() - 1) {
				lastEntropy -= engine.entropy;
				progress = -1;
			}
		}
	}

	@Override
	public void informRefutation(GameState gs, Integer refutingPlayerID,
			Card refutingCard) {
		engine.mustExist(refutingCard, this.IDToOpponentIdx.get(refutingPlayerID));
		if (progress >= 0) {
			lastEntropy -= engine.entropy;
			progress = -1;
		}
	}

	@Override
	public void informRefutationHidden(GameState gs, Integer refutingPlayerID,
			Integer suggestingPlayerID,
			Triplet<Room, Suspect, Weapon> suggestion) {
		engine.opponentReaction(new Card[] {
									new RoomCard(suggestion.getValue0()),
				 					new SuspectCard(suggestion.getValue1()),
				 					new WeaponCard(suggestion.getValue2())
			   				    },
			   				    this.IDToOpponentIdx.get(refutingPlayerID),
			   				    true);
	}

	@Override
	public void gameEnded(GameState gs, Integer winnerID,
			Triplet<Room, Suspect, Weapon> winningAccusation) {
		/*if (winnerID == gs.getOwnID(this))
			System.out.println("Player " + gs.getOwnID(this) + ": We won!");
		else 
			System.out.println("Player " + gs.getOwnID(this) + ": We lost...");*/
	}

	@Override
	public void informFailedAccusation(GameState gs, Integer failedPlayerID,
			Triplet<Room, Suspect, Weapon> failedAccusation) {
		// TODO: add in accusation stuff
	}

	@Override
	public Triplet<Room, Suspect, Weapon> accuse(GameState gs) {
		for (int r = 0; r < 9; r++) {
			for (int s = 0; s < 6; s++)
				for (int w = 0; w < 6; w++) {
					if (engine.envProb[w][s][r] > 0.95)
						return new Triplet<Room, Suspect, Weapon>(Room.values()[r], 
								Suspect.values()[s], 
								Weapon.values()[w]);
				}
		}
		return null;
	}

}
